package com.ruoyi.iot.controller;

import com.ruoyi.iot.domain.Coldefinetable;
import com.ruoyi.iot.service.IColdefinetableService;
import com.ruoyi.iot.service.InfluxService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class OpcUaAsyncCollector {

    // 用于缓存每个 endpoint（IP）的 OPC UA 客户端连接，避免重复创建
    private final Map<String, OpcUaClient> clientMap = new ConcurrentHashMap<>();

    // 缓存所有的调度任务，便于取消和热更新
    private final List<ScheduledFuture<?>> tasks = new CopyOnWriteArrayList<>();

    // 线程池用于执行 采集任务（推荐大小按 CPU 核心数 * 2~4）
    private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(50);

    @Autowired
    private IColdefinetableService pointService;

    @Autowired
    private InfluxService influxService;

    /**
     * 停止所有采集任务并关闭连接
     */
    public void stopAllPoints() {
        // 取消所有已有任务
        tasks.forEach(t -> t.cancel(true));
        tasks.clear();

        // 关闭所有 OPC UA 客户端连接
        clientMap.values().forEach(client -> {
            try {
                if (client != null) {
                    client.disconnect().get(5, TimeUnit.SECONDS);
                }
            } catch (Exception e) {
                log.error("关闭 OPC UA 连接失败: {}", e.getMessage());
            }
        });
        clientMap.clear();
        log.info("所有采集任务已停止，连接已关闭!");
    }

    /**
     * 热更新采集点位：重新加载数据库点位并开始采集
     */
    public void reloadAllPoints() {
        // 取消所有已有任务（上一次采集）
        tasks.forEach(t -> t.cancel(true));
        tasks.clear();

        // 从数据库获取所有点位（你也可以筛选 enabled = 1）
        List<Coldefinetable> points = pointService.selectColdefinetableList(new Coldefinetable());

        // 按 IP 分组，每个 IP 对应一个 endpoint（即一个 OPC UA 连接）
        Map<String, List<Coldefinetable>> grouped = points.stream()
                .collect(Collectors.groupingBy(Coldefinetable::getIp));

        // 遍历每个 endpoint 分组
        grouped.forEach((ip, pointList) -> {
            String endpoint = "opc.tcp://" + ip + ":4840";

            // 为每个 endpoint 创建或复用一个 OPC UA 客户端连接
            OpcUaClient client = clientMap.computeIfAbsent(endpoint, ep -> {
                try {
                    OpcUaClient c = OpcUaClient.create(ep);
                    c.connect().get(); // 同步连接
                    return c;
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            });

            // 如果连接成功，给该 endpoint 下的每个点位创建调度任务
            if (client != null) {
                for (Coldefinetable point : pointList) {
                    // 从 remark2 中读取采样周期（单位 ms），默认为 1000
                    int rate = 5000;
                    try {
                        if (point.getRemark2() != null) {
                            rate = Integer.parseInt(point.getRemark2());
                        }
                    } catch (Exception ignored) {}

                    String nodeIdStr = point.getSrc(); // 点位节点标识符

                    // 定时异步读取任务（通过线程池调度）
                    ScheduledFuture<?> task = executor.scheduleAtFixedRate(() -> {
                        client.readValue(0, TimestampsToReturn.Both, NodeId.parse(nodeIdStr))
                                .thenAccept(value -> {
                                    Object val = value.getValue().getValue(); // 实际值
                                    Instant ts = value.getSourceTime() != null ?
                                            value.getSourceTime().getJavaDate().toInstant() : Instant.now();
                                    log.info("读取成功: {}{}", nodeIdStr, value.getValue());
                                    // 写入到 InfluxDB
                                    influxService.writeData(point, val, ts);
                                }).exceptionally(ex -> {
                                    log.error("读取失败: {} - {}", point.getName(), ex.getMessage());
                                    return null;
                                });
                    }, 0, rate, TimeUnit.MILLISECONDS);
                    tasks.add(task);
                }
            }
        });
    }

}
